#compdef trizen

#
# /usr/share/zsh/site-functions/_trizen
#

typeset -A opt_args
setopt extendedglob

# we use zstat to check ctime quickly because pacman doesn't always set
# mtime and `test 'file' -nt 'file'` uses mtime
if zmodload -F zsh/stat b:zstat 2>/dev/null; then
    # check all databases against the cache file
    function _trizen_all_packages_caching_policy() {
        [[ ! -f "$1" ]] && return 0
        for file in /var/lib/pacman/sync/*.db; do
            if [[ "$(zstat +ctime "$file")" -gt "$(zstat +ctime "$1")" ]]; then
                return 0
            fi
        done
        return 1
    }

    # check the database being completed against it's cache file
    function _trizen_repo_packages_caching_policy() {
        [[ ! -f "$1" ]] && return 0
        local file="/var/lib/pacman/sync/${words[CURRENT]%/*}.db"
        if [[ "$(zstat +ctime "$file")" -gt "$(zstat +ctime "$1")" ]]; then
            return 0
        fi
        return 1
    }
else
    # return 0 is implicit
    function _trizen_all_packages_caching_policy() {}
    function _trizen_repo_packages_caching_policy() {}
fi


# options for passing to _arguments: main trizen commands
_trizen_opts_commands=(
    {-S,--sync}'[Synchronize packages]'
    {-D,--database}'[Operate on the package database]'
    {-Q,--query}'[Query the package database]'
    {-R,--remove}'[Remove packages from the system]'
    {-T,--deptest}'[Check if dependencies are installed]'
    {-U,--upgrade}'[Upgrade a package]'

    {-h,--help}'[Display usage]'
    '--version[Display trizen version]'
    '(-h --help)'{-h,--help}'[Display usage]'
)

_trizen_opts_extended=(
    {-a,--aur}'[Only AUR operations]'
    '--noedit[Do not prompt to edit files]'
)

# options for passing to _arguments: options common to all commands
_trizen_opts_common=(
    {-h,--help}'[Display syntax for the given operation]'
    '--clone-dir[Alternate package cache location]:cache_location:_files -/'
    '--debug[Activate the debug/verbose mode]'
    '--noconfirm[Do not ask for confirmation]'
    {-r,--regular}'[Use only the regular repositories]'
)

# options for passing to _arguments: options for --upgrade commands
_trizen_opts_pkgfile=(
    '*-d[Skip dependency checks]'
    '*--nodeps[Skip dependency checks]'
    '--dbonly[Only remove database entry, do not remove files]'
    '--force[Overwrite conflicting files]'
    '--needed[Do not reinstall up-to-date packages]'
    '*:package file:_files -g "*.pkg.tar*~*.sig(.,@)"'
)

# options for passing to _arguments: subactions for --query command
_trizen_opts_query_actions=(
    '(-Q --query)'{-Q,--query}
    {-g,--groups}'[View all members of a package group]:*:package groups:->query_group'
    {-o,--owns}'[Query the package that owns a file]:file:_files'
    {-p,--file}'[Package file to query]:*:package file:->query_file'
    {-s,--search}'[Search package names and descriptions]:*:search text:->query_search'
)

# options for passing to _arguments: options for --query and subcommands
_trizen_opts_query_modifiers=(
    {-c,--changelog}'[List package changelog]'
    {-d,--deps}'[List packages installed as dependencies]'
    {-e,--explicit}'[List packages explicitly installed]'
    {\*-i,\*--info}'[View package information]'
    {\*-k,\*--check}'[Check package files]'
    {-l,--list}'[List package contents]'
    {-m,--foreign}'[List installed packages not found in sync db(s)]'
    {-n,--native}'[List installed packages found in sync db(s)]'
    {-t,--unrequired}'[List packages not required by any package]'
    {-u,--upgrades}'[List packages that can be upgraded]'
    {-q,--quiet}'[Show less information]'
)

# options for passing to _arguments: options for --remove command
_trizen_opts_remove=(
    {-c,--cascade}'[Remove all dependent packages]'
    {*-d,*--nodeps}'[Skip dependency checks]'
    {-n,--nosave}'[Remove protected configuration files]'
    {\*-s,\*--recursive}'[Remove dependencies not required by other packages]'
    '--dbonly[Only remove database entry, do not remove files]'
    '*:installed package:_trizen_completions_installed_packages'
)

_trizen_opts_database=(
    '--asdeps[mark packages as non-explicitly installed]'
    '--asexplicit[mark packages as explicitly installed]'
    '*:installed package:_trizen_completions_installed_packages'
)

# options for passing to _arguments: options for --sync command
_trizen_opts_sync_actions=(
    '(-S --sync)'{-S,--sync}
    {\*-c,\*--clean}'[Clean the cache directory]:\*:clean:->sync_clean'
    '--needed[Do not reinstall up-to-date packages]'
)

# options for passing to _arguments: options for --sync command
_trizen_opts_sync_modifiers=(
    {\*-i,\*--info}'[View package information]'
    {-m,--maintainer}'[Show packages maintained by <username>]'
    {-y,--refresh}'[Refresh package databases]'
    {-u,--sysupgrade}'[Upgrade installed packages]'
    {-s,--search}'[Search for packages]'
    {-p,--pkgbuild}'[Show PKGBUILD only]'
    {-l,--local}'[Build and installs packages from `pwd`]'
    {-q,--quiet}'[Show less information]'
    '--asdeps[Install packages as non-explicitly installed]'
    '--asexplicit[Install packages as explicitly installed]'
    '--devel[Update VCS packages during -Su]'
    '--show-ood[Show out-of-date flagged packages during -Su]'
    '--noinfo[Do not display package info after cloning]'
    '--nopull[Do not `git pull` new changes]'
    '--noedit[Do not prompt to edit files]'
    '--ignore[Space-separated list of packages to ignore]'
    '--nobuild[Do not build packages (implies --noedit)]'
    '--noinstall[Do not install packages after building]'
    '--skipinteg[Pass the `--skipinteg` argument to `makepkg`]'
    '--force[Overwrite conflicting files]'
)

# handles --help subcommand
_trizen_action_help() {
    _arguments -s : \
        "$_trizen_opts_commands[@]"
}

# handles cases where no subcommand has yet been given
_trizen_action_none() {
    _arguments -s : \
        "$_trizen_opts_commands[@]"
}

# handles --query subcommand
_trizen_action_query() {
    local context state line
    typeset -A opt_args

    case $state in
        query_file)
            _arguments -s : \
                "$_trizen_opts_common[@]" \
                "$_trizen_opts_query_modifiers[@]" \
                '*:package file:_files -g "*.pkg.tar*~*.sig(.,@)"'
            ;;
        query_group)
            _arguments -s : \
                "$_trizen_opts_common[@]" \
                "$_trizen_opts_query_modifiers[@]" \
                '*:groups:_trizen_completions_installed_groups'
            ;;
        query_owner)
            _arguments -s : \
                "$_trizen_opts_common[@]" \
                "$_trizen_opts_query_modifiers[@]" \
                '*:file:_files'
            ;;
        query_search)
            _arguments -s : \
                "$_trizen_opts_common[@]" \
                "$_trizen_opts_query_modifiers[@]" \
                '*:search text: '
            ;;
        *)
            _arguments -s : \
                "$_trizen_opts_common[@]" \
                "$_trizen_opts_query_actions[@]" \
                "$_trizen_opts_query_modifiers[@]" \
                '*:package:_trizen_completions_installed_packages'
            ;;
    esac
}

# handles --remove subcommand
_trizen_action_remove() {
    _arguments -s : \
        '(--remove -R)'{-R,--remove} \
        "$_trizen_opts_common[@]" \
        "$_trizen_opts_remove[@]"
}

# handles --database subcommand
_trizen_action_database() {
    _arguments -s : \
        '(--database -D)'{-D,--database} \
        "$_trizen_opts_common[@]" \
        "$_trizen_opts_database[@]"
}

_trizen_action_deptest () {
    _arguments -s : \
        '(--deptest)-T' \
        "$_trizen_opts_common[@]" \
        ":packages:_trizen_all_packages"
}

# handles --sync subcommand
_trizen_action_sync() {
    local context state line
    typeset -A opt_args
    if (( $+words[(r)--clean] )); then
        state=sync_clean
    elif (( $+words[(r)--groups] )); then
        state=sync_group
    elif (( $+words[(r)--search] )); then
        state=sync_search
    fi

    case $state in
        sync_clean)
            _arguments -s : \
                {\*-c,\*--clean}'[Remove old packages from cache]' \
                "$_trizen_opts_common[@]" \
                "$_trizen_opts_sync_modifiers[@]"
                ;;
        sync_group)
            _arguments -s : \
                "$_trizen_opts_common[@]" \
                "$_trizen_opts_sync_modifiers[@]"
            ;;
        sync_search)
            _arguments -s : \
                "$_trizen_opts_common[@]" \
                "$_trizen_opts_sync_modifiers[@]" \
                '*:search text: '
            ;;
        *)
            _arguments -s : \
                "$_trizen_opts_common[@]" \
                "$_trizen_opts_extended[@]" \
                "$_trizen_opts_sync_actions[@]" \
                "$_trizen_opts_sync_modifiers[@]" \
                '*:packages:'$completion_repo
            ;;
    esac
}

# handles --upgrade subcommand
_trizen_action_upgrade() {
    _arguments -s : \
        '(-U --upgrade)'{-U,--upgrade} \
        "$_trizen_opts_common[@]" \
        "$_trizen_opts_pkgfile[@]"
}

# handles --version subcommand
_trizen_action_version() {
    # no further arguments
    return 0
}

# provides completions for package groups
_trizen_completions_all_groups() {
    local -a cmd groups
    _trizen_get_command
    groups=( $(_call_program groups $cmd[@] -Sg) )
    typeset -U groups
    compadd "$@" -a groups
}

# provides completions for packages available from repositories
# these can be specified as either 'package' or 'repository/package'
_trizen_completions_all_packages() {
    local curcontext="${curcontext}" cache_name repo
    local -a cmd packages repositories packages_long

    _trizen_get_command

    if compset -P1 '*/*'; then
        curcontext="${curcontext}_repo_packages"
        zstyle -s ":completion:${curcontext}:" cache-policy update_policy
        [[ -z "$update_policy" ]] && zstyle ":completion:${curcontext}:" \
            cache-policy _trizen_repo_packages_caching_policy

        repo="${words[CURRENT]%/*}"
        cache_name="pacman_repo_packages_$repo"
        if _cache_invalid "$cache_name" || ! _retrieve_cache "$cache_name"; then
            packages=( $(_call_program packages $cmd[@] -Sql "$repo") )
            typeset -U packages
            _store_cache "$cache_name" packages
        fi
        _wanted repo_packages expl "repository/package" compadd ${(@)packages}
    else
        curcontext="${curcontext}_all_packages"
        zstyle -s ":completion:${curcontext}:" cache-policy update_policy
        [[ -z "$update_policy" ]] && zstyle ":completion:${curcontext}:" \
            cache-policy _trizen_all_packages_caching_policy

        cache_name='pacman_packages'
        if _cache_invalid "$cache_name" || ! _retrieve_cache "$cache_name"; then
            packages=( $(_call_program packages $cmd[@] -Sql) )
            typeset -U packages
            _store_cache "$cache_name" packages
        fi
        _wanted packages expl "packages" compadd - "${(@)packages}"

        repositories=(${(o)${${${(M)${(f)"$(</etc/pacman.conf)"}:#\[*}/\[/}/\]/}:#options})
        typeset -U repositories
        _wanted repo_packages expl "repository/package" compadd -S "/" $repositories
    fi
}

_trizen_completions_aur_packages() {
    zstyle -T ":completion:${curcontext}:" remote-access || return 1
    local -a aur_packages
    aur_packages=($(_call_program packages trizen --nocolors -Ssq $words[CURRENT] 2>/dev/null))
    _wanted aur_packages expl "aur packages" compadd - "${(@)aur_packages}"
}

# provides completions for package groups
_trizen_completions_installed_groups() {
    local -a cmd groups
    _trizen_get_command
    groups=(${(o)${(f)"$(_call_program groups $cmd[@] -Qg)"}% *})
    typeset -U groups
    compadd "$@" -a groups
}

# provides completions for installed packages
_trizen_completions_installed_packages() {
    local -a packages
    packages_long=(/var/lib/pacman/local/*(/))
    packages=( ${${packages_long#/var/lib/pacman/local/}%-*-*} )
    compadd "$@" -a packages
}

_trizen_all_packages() {
    _alternative : \
        'localpkgs:local packages:_trizen_completions_installed_packages' \
        'aurpkgs:aur packages:_trizen_completions_aur_packages' \
        'repopkgs:repository packages:_trizen_completions_all_packages'
}

_trizen_remote_packages() {
    _alternative : \
        'aurpkgs:aur packages:_trizen_completions_aur_packages' \
        'repopkgs:repository packages:_trizen_completions_all_packages'
}

# provides completions for repository names
_trizen_completions_repositories() {
    local -a cmd repositories
    repositories=(${(o)${${${(M)${(f)"$(</etc/pacman.conf)"}:#\[*}/\[/}/\]/}:#options})
    # Uniq the array
    typeset -U repositories
    compadd "$@" -a repositories
}

# builds command for invoking pacman in a _call_program command - extracts
# relevant options already specified (config file, etc)
# $cmd must be declared by calling function
_trizen_get_command() {
    # this is mostly nicked from _perforce
    cmd=( "pacman" "2>/dev/null")
    integer i
    for (( i = 2; i < CURRENT - 1; i++ )); do
        if [[ ${words[i]} = "--config" || ${words[i]} = "--root" ]]; then
            cmd+=( ${words[i,i+1]} )
        fi
    done
}

# main dispatcher
local -a args cmds;
local tmp
args=( ${${${(M)words:#-*}#-}:#-*} )
for tmp in $words; do
    cmds+=("${${_trizen_opts_commands[(r)*$tmp\[*]%%\[*}#*\)}")
done

# handle repository filter
if (( $+words[(r)--aur] || $+words[(r)-S*a*] )); then
    completion_repo='_trizen_completions_aur_packages'
elif (( $+words[(r)--repo] || $+words[(r)-S*r*] )); then
    completion_repo='_trizen_completions_all_packages'
else
    completion_repo='_trizen_remote_packages'
fi

case $args in
    h*)
        if (( ${(c)#args} <= 1 && ${(w)#cmds} <= 1 )); then
            _trizen_action_help
        else
            _message "no more arguments"
        fi
        ;;
    *h*)
        _message "no more arguments"
        ;;
    D*)
        _trizen_action_database
        ;;
    Q*g*) # ipkg groups
        _arguments -s : \
            "$_trizen_opts_common[@]" \
            "$_trizen_opts_query_modifiers[@]" \
            '*:groups:_trizen_completions_installed_groups'
        ;;
    Q*o*) # file
        _arguments -s : \
            "$_trizen_opts_common[@]" \
            "$_trizen_opts_query_modifiers[@]" \
            '*:package file:_files'
        ;;
    Q*p*) # file *.pkg.tar*
        _arguments -s : \
            "$_trizen_opts_common[@]" \
            "$_trizen_opts_query_modifiers[@]" \
            '*:package file:_files -g "*.pkg.tar*~*.sig(.,@)"'
        ;;
    T*)
        _trizen_action_deptest
        ;;
    Q*)
        _trizen_action_query
        ;;
    R*)
        _trizen_action_remove
        ;;
    S*c*) # no completion
        _arguments -s : \
            '(-c --clean)'{\*-c,\*--clean}'[Remove all files from the cache]' \
            "$_trizen_opts_common[@]"
        ;;
    S*l*) # repos
        _arguments -s : \
            "$_trizen_opts_common[@]" \
            "$_trizen_opts_sync_modifiers[@]" \
            '*:package repo:_trizen_completions_repositories' \
        ;;
    S*g*) # pkg groups
        _arguments -s : \
            "$_trizen_opts_common[@]" \
            "$_trizen_opts_sync_modifiers[@]" \
            '*:package group:_trizen_completions_all_groups'
        ;;
    S*s*)
        _arguments -s : \
            "$_trizen_opts_common[@]" \
            "$_trizen_opts_sync_modifiers[@]" \
            '*:search text: '
        ;;
    S*u*)
        _arguments -s : \
            '--devel[Consider AUR development packages upgrade]' \
            "$_trizen_opts_common[@]" \
            "$_trizen_opts_extended[@]" \
            "$_trizen_opts_sync_actions[@]" \
            "$_trizen_opts_sync_modifiers[@]" \
            '*:packages:'$completion_repo
        ;;
    S*)
        _trizen_action_sync
        ;;
    T*)
         _arguments -s : \
            '-T' \
            "$_trizen_opts_common[@]" \
            ":packages:_trizen_all_packages"
        ;;
    U*)
        _trizen_action_upgrade
        ;;
    V*)
        _trizen_action_version
        ;;
    *)

        case ${(M)words:#--*} in
            *--help*)
                if (( ${(w)#cmds} == 1 )); then
                    _trizen_action_help
                else
                    return 0;
                fi
                ;;
            *--sync*)
                _trizen_action_sync
                ;;
            *--query*)
                _trizen_action_query
                ;;
            *--remove*)
                _trizen_action_remove
                ;;
            *--deptest*)
                _trizen_action_deptest
                ;;
            *--database*)
                _trizen_action_database
                ;;
            *--version*)
                _trizen_action_version
                ;;
            *--upgrade*)
                _trizen_action_upgrade
                ;;
            *)
                _trizen_action_none
                ;;
        esac
        ;;
esac

# vim:set ts=4 sw=2 et:
